/* Copyright (c) 2011 Damian Kmiecik
   All rights reserved.

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:

   * Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in
     the documentation and/or other materials provided with the
     distribution.
   * Neither the name of the copyright holders nor the names of
     contributors may be used to endorse or promote products derived
     from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  POSSIBILITY OF SUCH DAMAGE. */

#include <avr/io.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include <avr/eeprom.h>
#include <avr/pgmspace.h>
#include <stdio.h>
#include "httpd.h"
#include "config.h"
#include "main.h"
#include "files.h"
#include "flash.h"
#include "stdout.h"
#include "timer.h"
#include "ethernet.h"
#include "adc.h"
#include "usart.h"
#include "clock.h"
#include "enc28j60.h"
#include "enc28j60reg.h"
#include "1wire.h"

#ifdef USE_HTTPD

#ifndef HTTPD_DEBUG
	#define HTTPD_DEBUG(...)
#endif

// HTTP Headers
prog_uint8_t headerMain[] = { "HTTP/1.1 200 OK\r\n"
	"Server: Damian Kmiecik's AVR WebServer\r\n"
	"Connection: Close\r\n"
	"Content-Length: " };
prog_uint8_t headerNotFound[] = { "HTTP/1.1 404 Not Found\r\n"
	"Server: Damian Kmiecik's AVR WebServer\r\n"
	"Connection: Close\r\n"
	"Content-Length: 0\r\n"
	"\r\n" };
prog_uint8_t headerNeedAuth[] = { "HTTP/1.1 401 Authorization Required\r\n"
	"Server: Damian Kmiecik's AVR WebServer\r\n"
	"Connection: Close\r\n"
	"WWW-Authenticate: Basic realm=\"Secure Area\"\r\n"
	"Content-Length: 0\r\n"
	"\r\n" };
prog_uint8_t headerContent[] = { "\r\nContent-Type: " };
prog_uint8_t headerMimeHtml[] = { "text/html;charset=UTF8\r\n\r\n" };
prog_uint8_t headerMimeCss[] = { "text/css;charset=UTF8\r\n\r\n" };
prog_uint8_t headerMimeJpg[] = { "image/jpeg\r\n\r\n" };
prog_uint8_t headerMimeGif[] = { "image/gif\r\n\r\n" };
prog_uint8_t headerMimePlain[] = { "text/plain\r\n\r\n" };

struct httpd_data_s {
	uint8_t file;
	uint8_t post		:2;
	uint8_t status		:2;
	uint8_t send		:2;
	uint8_t auth		:1;
	uint16_t data_size;
	uint16_t data_sent;
};

#define HTTPD_EMPTY_STATE		0x00
#define HTTPD_UPLOAD_STATE		0x01
#define HTTPD_UPLOAD_H_STATE	0x02
#define HTTPD_HEADER_STATE		0x03

#define HTTPD_SEND_NOT_FOUND	0x01
#define HTTPD_SEND_OK			0x02
#define HTTPD_SEND_AUTH			0x03

static struct httpd_data_s *appstatus;
void checkAuth(void);
inline void headerPrepare(void);

void getPostVars(void)
{
	// PORTA0
	uint8_t *posPtr = strstr_P(&ethBuffer[TCP_DATA], PSTR("prta0"));
	if (posPtr) {
		PORTA |= _BV(PA0);
		HTTPD_DEBUG("HTTPD post porta0 on\n", NULL);
	} else {
		PORTA &= ~_BV(PA0);
		HTTPD_DEBUG("HTTPD post porta0 off\n", NULL);
	}
	// PORTA1
	posPtr = strstr_P(&ethBuffer[TCP_DATA], PSTR("prta1"));
	if (posPtr) {
		PORTA |= _BV(PA1);
		HTTPD_DEBUG("HTTPD post porta1 on\n", NULL);
	} else {
		PORTA &= ~_BV(PA1);
		HTTPD_DEBUG("HTTPD post porta1 off\n", NULL);
	}
	// PORTA2
	posPtr = strstr_P(&ethBuffer[TCP_DATA], PSTR("prta2"));
	if (posPtr) {
		PORTA |= _BV(PA2);
		HTTPD_DEBUG("HTTPD post porta2 on\n", NULL);
	} else {
		PORTA &= ~_BV(PA2);
		HTTPD_DEBUG("HTTPD post porta2 off\n", NULL);
	}
	// PORTA3
	posPtr = strstr_P(&ethBuffer[TCP_DATA], PSTR("prta3"));
	if (posPtr) {
		PORTA |= _BV(PA3);
		HTTPD_DEBUG("HTTPD post porta3 on\n", NULL);
	} else {
		PORTA &= ~_BV(PA3);
		HTTPD_DEBUG("HTTPD post porta3 off\n", NULL);
	}
	// PORTA4
	posPtr = strstr_P(&ethBuffer[TCP_DATA], PSTR("prta4"));
	if (posPtr) {
		PORTA |= _BV(PA4);
		HTTPD_DEBUG("HTTPD post porta4 on\n", NULL);
	} else {
		PORTA &= ~_BV(PA4);
		HTTPD_DEBUG("HTTPD post porta4 off\n", NULL);
	}
	// PORTA5
	posPtr = strstr_P(&ethBuffer[TCP_DATA], PSTR("prta5"));
	if (posPtr) {
		PORTA |= _BV(PA5);
		HTTPD_DEBUG("HTTPD post porta5 on\n", NULL);
	} else {
		PORTA &= ~_BV(PA5);
		HTTPD_DEBUG("HTTPD post porta5 off\n", NULL);
	}
	// PORTA6
	posPtr = strstr_P(&ethBuffer[TCP_DATA], PSTR("prta6"));
	if (posPtr) {
		PORTA |= _BV(PA6);
		HTTPD_DEBUG("HTTPD post porta6 on\n", NULL);
	} else {
		PORTA &= ~_BV(PA6);
		HTTPD_DEBUG("HTTPD post porta6 off\n", NULL);
	}
	// PORTA7
	posPtr = strstr_P(&ethBuffer[TCP_DATA], PSTR("prta7"));
	if (posPtr) {
		PORTA |= _BV(PA7);
		HTTPD_DEBUG("HTTPD post porta7 on\n", NULL);
	} else {
		PORTA &= ~_BV(PA7);
		HTTPD_DEBUG("HTTPD post porta7 off\n", NULL);
	}
}

prog_char varsFileContent[] = {
	"var porta=0x00;\r\n"
	"var portd=0x00;\r\n"
	"var pina=0x00;\r\n"
	"var pind=0x00;\r\n"
	"var adc=new Array();\r\n"
};
prog_char varsFileContent2[] = {
	"var owire=new Array();\r\n"
};
prog_char varsFileContent3[] = {
	"owire[0x00]=  0x00;\r\n"
};
prog_char varsFileContent4[] = {
	"adc[0x00]= 0x00;\r\n"
};

char nibbleToHexChar(uint8_t h)
{
	h &= 0x0F;
	if (h > 9)
		return 'A'+(h-10);
	return '0'+h;
}

void documentVars(void)
{
	HTTPD_DEBUG("HTTPD vars\n", NULL);
	memcpy_P(&ethBuffer[TCP_DATA], varsFileContent, sizeof(varsFileContent)-1);
	// PORTA
	ethBuffer[TCP_DATA+12] = nibbleToHexChar(PORTA>>4);
	ethBuffer[TCP_DATA+13] = nibbleToHexChar(PORTA);
	// PORTD
	ethBuffer[TCP_DATA+29] = nibbleToHexChar(PORTD>>4);
	ethBuffer[TCP_DATA+30] = nibbleToHexChar(PORTD);
	// PINA
	ethBuffer[TCP_DATA+45] = nibbleToHexChar(PINA>>4);
	ethBuffer[TCP_DATA+46] = nibbleToHexChar(PINA);
	// PIND
	ethBuffer[TCP_DATA+61] = nibbleToHexChar(PIND>>4);
	ethBuffer[TCP_DATA+62] = nibbleToHexChar(PIND);
	// ADC
#ifdef USE_ADC
	uint16_t p = TCP_DATA+sizeof(varsFileContent)-1;
	memcpy_P(&ethBuffer[p], varsFileContent4, sizeof(varsFileContent4)-1);
	uint8_t adcr = adcChannel(7);
	ethBuffer[p+13] = nibbleToHexChar(adcr>>4);
	ethBuffer[p+14] = nibbleToHexChar(adcr);
	//ethBuffer[p+6] = '0';
	ethBuffer[p+7] = '1';
	p += sizeof(varsFileContent4)-1;
#endif
	// 1WIRE
	memcpy_P(&ethBuffer[p], varsFileContent2, sizeof(varsFileContent2)-1);
#ifdef USE_1WIRE
	for (uint8_t i = 0; i < OW_cnt;)
	{
		memcpy_P(
			&ethBuffer[p+sizeof(varsFileContent2)+(i*(sizeof(varsFileContent3)-1))-1],
			varsFileContent3,
			sizeof(varsFileContent3)-1
		);
		int t = OWReadTemperature(i);
		i++;
		ethBuffer[p+sizeof(varsFileContent2)+(i*(sizeof(varsFileContent3)-1))-14] = nibbleToHexChar((i-1)>>4);
		ethBuffer[p+sizeof(varsFileContent2)+(i*(sizeof(varsFileContent3)-1))-13] = nibbleToHexChar(i-1);
		if (t < 0)
		{
			ethBuffer[p+sizeof(varsFileContent2)+(i*(sizeof(varsFileContent3)-1))-9] = '-';
			t &= 0x7F;
		}
		ethBuffer[p+sizeof(varsFileContent2)+(i*(sizeof(varsFileContent3)-1))-6] = nibbleToHexChar(t>>4);
		ethBuffer[p+sizeof(varsFileContent2)+(i*(sizeof(varsFileContent3)-1))-5] = nibbleToHexChar(t);
	}
#endif
}

void httpd(void)
{
	appstatus = (struct httpd_data_s*)tcp_conn->app_status;
	if (tcp_status.status & TCP_CONNECTED)
	{
		// Clear application data
		memset(appstatus, 0x00, sizeof(struct httpd_data_s));
		appstatus->status = HTTPD_EMPTY_STATE;
		HTTPD_DEBUG("HTTPD new connection!\n", NULL);
		return;
	}
	if (tcp_status.status & TCP_POOL)
	{
		return;
	}
	if (tcp_status.status & TCP_TIMEDOUT)
	{
		HTTPD_DEBUG("HTTPD time-outed!\n", NULL);
		TCPindexDel();
		return;
	}
	if (tcp_status.status & (TCP_CLOSED|TCP_ABORTED))
	{
		HTTPD_DEBUG("HTTPD closing on request!\n", NULL);
		return;
	}
	if (tcp_status.status & TCP_NEWDATA)
	{
		// There is no data?
//		if (!strlen(&ethBuffer[TCP_DATA]))
//		{
//			HTTPD_DEBUG("HTTPD empty request!\n", NULL);
//			return;
//		}
		// First data
		// We except HTTP command POST or GET
		if (appstatus->status == HTTPD_EMPTY_STATE)
		{
			// POST or GET?
			uint8_t *cmdPtr = strcasestr_P(
				&ethBuffer[TCP_DATA],
				PSTR("POST")
			);
			if (cmdPtr)
			{
				// POST
				HTTPD_DEBUG("HTTPD post\n", NULL);
				// Get post var & process
				appstatus->post = TRUE;
				cmdPtr += 4;
			} else {
				// GET
				cmdPtr = strcasestr_P(
					&ethBuffer[TCP_DATA],
					PSTR("GET")
				);
				if (cmdPtr == NULL)
				{
					HTTPD_DEBUG("HTTPD invalid command!\n", NULL);
					return;
				}
				HTTPD_DEBUG("HTTPD get\n", NULL);
				//appstatus->post = FALSE;
				cmdPtr += 3;
			}
			cmdPtr += 2;
			char *filename = cmdPtr;
			if (filename[0] == ' ')
				memcpy_P(filename, PSTR("index.html "), 11);
			// Change last ' ' to '\0'
			while (*++cmdPtr != ' ');
			*cmdPtr = '\0';
			// Serach file
			appstatus->file = fileSearch(filename);
			HTTPD_DEBUG("HTTPD open %s\n", filename);
			// VARS.JS
			if (strcmp_P(filename, PSTR("vars.js")) == 0)
				appstatus->file = FILE_VARS;
			*cmdPtr = ' ';
			//appstatus->auth = FALSE;
			checkAuth();
			appstatus->status = HTTPD_HEADER_STATE;
		}
		// We must send header
		if (appstatus->status == HTTPD_HEADER_STATE)
		{
			// get data length
			uint16_t dataSize = strlen(&ethBuffer[TCP_DATA]);
			checkAuth();
			// End of HTTPD request ends with '\r\n\r\n', so...
			// Check is ready
			if ((strcasestr_P(&ethBuffer[TCP_DATA], PSTR("\r\n\r\n")) == NULL) &&
				(appstatus->post != 2))
			{
				HTTPD_DEBUG("HTTPD wait\n", NULL);
				return;
			} else if((appstatus->post == 1) &&
				(strcmp_P(&ethBuffer[TCP_DATA+dataSize-4], PSTR("\r\n\r\n")) == 0))
			{
				HTTPD_DEBUG("HTTPD post wait\n", NULL);
				appstatus->post = 2;
				return;
			} else if((appstatus->post == 1) &&
				(strcmp_P(&ethBuffer[TCP_DATA+dataSize-4], PSTR("\r\n\r\n")) != 0))
			{
				appstatus->post = 2;
			}
			if (appstatus->post == 2)
			{
				appstatus->post = 0;
				getPostVars();
			}
			// Check authentication
			if (appstatus->auth == TRUE)
			{
				// Check file
				if (appstatus->file == FILE_NOT_FOUND)
				{
					HTTPD_DEBUG("HTTPD file not found\n", NULL);
					appstatus->send = HTTPD_SEND_NOT_FOUND;
				} else
					appstatus->send = HTTPD_SEND_OK;
			} else {
				HTTPD_DEBUG("HTTPD need auth\n", NULL);
				appstatus->send = HTTPD_SEND_AUTH;
			}
			appstatus->status = HTTPD_UPLOAD_H_STATE;
		}
	}
	// If data acked
	if (tcp_status.status & TCP_ACKED)
	{
		// Change mode
		if (appstatus->status == HTTPD_UPLOAD_H_STATE)
		{
			appstatus->data_sent = 0;
			if (appstatus->auth == FALSE)
			{
				memset(appstatus, 0x00, sizeof(struct httpd_data_s));
				appstatus->status = HTTPD_EMPTY_STATE;
				return;
			}
			appstatus->status = HTTPD_UPLOAD_STATE;
		// Increment data pointer
		} else if (appstatus->status == HTTPD_UPLOAD_STATE) {
			// Increment data pointer by this what was send and acked
			appstatus->data_sent += appstatus->data_size;
			// If all data sended
			fileById(appstatus->file);
			if (appstatus->data_sent == fileTable.size)
			{
				HTTPD_DEBUG("HTTPD finish\n", NULL);
//				memset(appstatus, 0x00, sizeof(struct httpd_data_s));
				appstatus->status = HTTPD_EMPTY_STATE;
				TCPportClose();
//				TCPindexDel();
				return;
			}
		}
	}
	// If retransmission occurs or data acked
	if (tcp_status.status & TCP_REXMIT)
	{
		tcp_conn->ack_counter = htons32(
			htons32(tcp_conn->ack_counter) - appstatus->data_size
		);
		appstatus->send = 0;
		HTTPD_DEBUG("HTTPD retransmission\n", NULL);
	}
	// Header upload
	if (appstatus->status == HTTPD_UPLOAD_H_STATE)
	{
		headerPrepare();
		HTTPD_DEBUG("HTTPD header sent\n", NULL);
		TCPsend(appstatus->data_size);
	}
	// or File upload
	if (appstatus->status == HTTPD_UPLOAD_STATE)
	{
		// Uploading
		// Open file
		fileById(appstatus->file);
		uint16_t s = 0;
		// Calculate data to be send
		if ((fileTable.size - appstatus->data_sent) > MAX_TCP_WINDOW)
		{
			s = MAX_TCP_WINDOW;
		} else {
			s = fileTable.size - appstatus->data_sent;
		}
		// If the MAGIC file chosen
		if (appstatus->file == FILE_VARS)
		{
			memset(&ethBuffer[TCP_DATA], ' ', MAX_TCP_WINDOW);
			// Create VARS
			documentVars();
		} else {
			// Write content
			flashRead(
				(fileTable.pointer+appstatus->data_sent),
				&ethBuffer[TCP_DATA],
				s
			);
		}
		appstatus->data_size = s;
		TCPsend(s);
	}
}

inline void headerPrepare(void)
{
	switch (appstatus->send)
	{
	case HTTPD_SEND_AUTH:
		memcpy_P(
			&ethBuffer[TCP_DATA],
			headerNeedAuth,
			sizeof(headerNeedAuth)
		);
		appstatus->data_size += sizeof(headerNeedAuth)-1;
		break;
	case HTTPD_SEND_NOT_FOUND:
		memcpy_P(
			&ethBuffer[TCP_DATA],
			headerNotFound,
			sizeof(headerNotFound)
		);
		appstatus->data_size += sizeof(headerNotFound)-1;
		break;
	case HTTPD_SEND_OK:
		memcpy_P(
			&ethBuffer[TCP_DATA],
			headerMain,
			sizeof(headerMain)
		);
		appstatus->data_size += sizeof(headerMain)-1;
		// Open file
		fileById(appstatus->file);
		// Write the rest of header
		// Write size
		itoa(fileTable.size, &ethBuffer[TCP_DATA+appstatus->data_size], 10);
		appstatus->data_size += strlen(&ethBuffer[TCP_DATA+appstatus->data_size]);
		// Write mime
		memcpy_P(
			&ethBuffer[TCP_DATA+appstatus->data_size],
			headerContent,
			sizeof(headerContent)
		);
		appstatus->data_size += sizeof(headerContent)-1;
		// copy filename to buffer
		char *filenamePtr = &fileTable.filename[strcspn_P(&fileTable.filename[0], PSTR("."))];
		filenamePtr++;
		prog_char *hPtr;
		uint8_t s;
		// Check mime
		if (strcmp_P(filenamePtr, PSTR("html")) == 0)
		{
			hPtr = headerMimeHtml;
			s = sizeof(headerMimeHtml);
		} else if (strcmp_P(filenamePtr, PSTR("css")) == 0) {
			hPtr = headerMimeCss;
			s = sizeof(headerMimeCss);
		} else if (strcmp_P(filenamePtr, PSTR("jpg")) == 0) {
			hPtr = headerMimeJpg;
			s = sizeof(headerMimeJpg);
		} else if (strcmp_P(filenamePtr, PSTR("gif")) == 0) {
			hPtr = headerMimeGif;
			s = sizeof(headerMimeGif);
		} else {
			hPtr = headerMimePlain;
			s = sizeof(headerMimePlain);
		}
		memcpy_P(
			&ethBuffer[TCP_DATA+appstatus->data_size],
			hPtr,
			s
		);
		appstatus->data_size += s-1;
	}
}

void checkAuth(void)
{
	char *authPtr = strcasestr_P(
		&ethBuffer[TCP_DATA],
		PSTR("Authorization")
	);
	if (authPtr)
	{
		// Check password
		authPtr += sizeof("Authorization: Basic ")-1;
		for (uint8_t i = 0; i < 255; i++)
		{
			if (authPtr[i] == '\r')
			{
				authPtr[i] = '\0';
				if (strcmp(&authPtr[0], &settings.httpd_pass[0]) == 0)
				{
					HTTPD_DEBUG("HTTPD auth\n", NULL);
					appstatus->auth = TRUE;
				}
				authPtr[i] = '\r';
				break;
			}
		}
	}
}

#endif /* USE_HTTPD */
